/**
 * This file implements MQTT-based communication functionality for retrieving
 * cellular module information, network status, and APN configuration
 * Provides structured data exchange with cellular modules using JSON format
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "mqtt_client.h"

#include "log_manage.h"
#include "main.h"

/*
    Memory leak testing command: valgrind --tool=memcheck --leak-check=yes --show-reachable=yes ./XhDataCenter

    About cJSON memory release:
        1. When releasing memory in cJSON, only the parent node needs to be released, 
           child nodes will be released automatically along with the parent node
        2. cJSON_Print will separately allocate memory, need to call cJSON_free to release memory
*/

/**
 * @brief Get current timestamp as formatted string
 * 
 * This function generates an ISO 8601 compliant timestamp string with millisecond precision
 * Format: YYYY-MM-DDTHH:MM:SS.sss+0800 (China timezone)
 * Uses system time functions to get current time with microsecond precision
 * 
 * @param buff Character buffer to store the timestamp string (must be pre-allocated)
 * @return int Returns 0 on success, -1 on failure (null buffer)
 */
int get_timestamp(char *buff)
{
    /* Time structure variables for time manipulation */
    time_t t;
    struct tm *t_tm;
    struct timeval tv;
    int usec_3;

    /* Validate input buffer is not null */
    if (NULL == buff)
        return -1;

    /* Get current time with microsecond precision */
    gettimeofday(&tv, NULL);
    /* Convert microseconds to milliseconds (keep only 3 highest digits) */
    usec_3 = tv.tv_usec / 1000; /* Only keep the highest 3 digits */

    /* Get current time in seconds since epoch */
    time(&t); /* This function get a timestamp in seconds_from_1970.microsoconds */
    /* Convert to local time structure */
    t_tm = localtime(&t);
    // printf("%s: %4d-%02d-%02d \r\n", __FUNCTION__, t_tm->tm_year + 1900, t_tm->tm_mon + 1, t_tm->tm_mday);

    /* Format timestamp string with year, month, day, hour, minute, second, and milliseconds */
    sprintf(buff, "%4d-%02d-%02dT%02d:%02d:%02d.%.3d+0800", t_tm->tm_year + 1900, t_tm->tm_mon + 1, t_tm->tm_mday,
            t_tm->tm_hour, t_tm->tm_min, t_tm->tm_sec, usec_3);

    return 0;
}

/**
 * @brief Extract integer value from cJSON object by key name
 * 
 * This function safely retrieves an integer value from a cJSON object
 * Handles null pointer validation and type conversion from string to integer
 * Uses cJSON library functions to access object properties
 * 
 * @param pVal Pointer to integer variable to store the extracted value
 * @param pjObject cJSON object containing the target key-value pair
 * @param strItem Key name of the integer value to extract
 * @return uint8_t Returns TRUE if successful, FALSE on failure
 */
uint8_t GetItemInt(int *pVal, const cJSON *pjObject, const char *strItem)
{
    /* Initialize return value to indicate failure by default */
    uint8_t bRet = FALSE;
    /* Validate input parameters are not null */
    if ((pjObject == NULL) || (strItem == NULL))
        return bRet;
    if (pVal == NULL)
        return bRet;

    /* Local variables for JSON parsing */
    cJSON *pjItem = NULL;
    char *strVal = NULL;
    /* Attempt to get the JSON item by key name */
    if ((pjItem = cJSON_GetObjectItem(pjObject, strItem)) != NULL)
    {
        /* Extract string value from the JSON item */
        if ((strVal = cJSON_GetStringValue(pjItem)) != NULL)
        {
            /* Convert string to integer and store in output parameter */
            *pVal = atoi(strVal);
            /* Set return value to indicate success */
            bRet = TRUE;
        }
    }
    return bRet;
}

/**
 * @brief Extract timestamp value from cJSON object by key name
 * 
 * This function retrieves a timestamp string from a cJSON object
 * Currently validates existence but doesn't perform actual timestamp conversion
 * Placeholder for future timestamp parsing functionality
 * 
 * @param pVal Pointer to time_t variable to store the extracted timestamp
 * @param pjObject cJSON object containing the target key-value pair
 * @param strItem Key name of the timestamp value to extract
 * @return uint8_t Returns TRUE if successful, FALSE on failure
 */
uint8_t GetItemTimeStamp(time_t *pVal, const cJSON *pjObject, const char *strItem)
{
    /* Initialize return value to indicate failure by default */
    uint8_t bRet = FALSE;
    /* Validate input parameters are not null */
    if ((pjObject == NULL) || (strItem == NULL))
        return bRet;
    if (pVal == NULL)
        return bRet;

    /* Local variables for JSON parsing */
    cJSON *pjItem = NULL;
    char *strVal = NULL;
    /* Attempt to get the JSON item by key name */
    if ((pjItem = cJSON_GetObjectItem(pjObject, strItem)) != NULL)
    {
        /* Extract string value from the JSON item */
        if ((strVal = cJSON_GetStringValue(pjItem)) != NULL)
        {
            /* Currently only validates existence, doesn't convert timestamp */
            bRet = TRUE;
        }
    }
    return bRet;
}

/**
 * @brief Extract string value from cJSON object by key name
 * 
 * This function safely retrieves a string value from a cJSON object
 * Performs buffer copying with strcpy to ensure proper string termination
 * Validates all input parameters before attempting extraction
 * 
 * @param pVal Character buffer to store the extracted string (must be pre-allocated)
 * @param pjObject cJSON object containing the target key-value pair
 * @param strItem Key name of the string value to extract
 * @return uint8_t Returns TRUE if successful, FALSE on failure
 */
uint8_t GetItemStr(char *pVal, const cJSON *pjObject, const char *strItem)
{
    /* Initialize return value to indicate failure by default */
    uint8_t bRet = FALSE;
    /* Validate input parameters are not null */
    if ((pjObject == NULL) || (strItem == NULL))
        return bRet;
    if (pVal == NULL)
        return bRet;

    /* Local variables for JSON parsing */
    cJSON *pjItem = NULL;
    char *strVal = NULL;

    /* Attempt to get the JSON item by key name */
    if ((pjItem = cJSON_GetObjectItem(pjObject, strItem)) != NULL)
    {
        /* Extract string value from the JSON item */
        if ((strVal = cJSON_GetStringValue(pjItem)) != NULL)
        {
            /* Copy string value to output buffer */
            strcpy(pVal, strVal);
            /* Set return value to indicate success */
            bRet = TRUE;
        }
    }
    return bRet;
}

/**
 * @brief Execute shell command and capture output
 * 
 * This function executes a shell command using popen and captures the output
 * Provides real-time logging of command execution progress
 * Handles command output in chunks to manage memory efficiently
 * 
 * @param cmd Shell command string to execute
 * @param result Buffer to store command output (must be pre-allocated)
 * @return uint8_t Returns TRUE if successful, FALSE on failure
 */
uint8_t shell_exec(char *cmd, char *result)
{
    /* Validate input parameters are not null */
    if (!result || !cmd)
        return FALSE;

    /* Execute shell command and create pipe for reading output */
    FILE *fp = popen(cmd, "r");
    /* Buffer for reading command output in chunks */
    char buffer[128] = {0};
    int len = 0;

    /* Check if command execution was successful */
    if (fp != NULL)
    {
        /* Read command output line by line */
        while (fgets(buffer, sizeof(buffer) - 1, fp) != 0)
        {
            /* Log each line of output for debugging */
            log_printf(MSG_DEBUG, "%s\n", buffer);
            /* Append buffer content to result string */
            memcpy(result + len, buffer, strlen(buffer));
            /* Update length counter for next copy operation */
            len += strlen(buffer);
            /* Clear buffer for next read operation */
            memset(buffer, 0x00, sizeof(buffer));
        }
    }
    else
    {
        /* Log error if command execution failed */
        log_printf(MSG_EOR, "ERROR: shell_exec:error!\n");
        /* Close pipe before returning */
        pclose(fp);
        return FALSE;
    }

    /* Close pipe after reading all output */
    pclose(fp);

    return TRUE;
}

/**
 * @brief Send MQTT request and receive response using mosquitto_rr
 * 
 * This function implements request-response pattern using MQTT messaging
 * Uses mosquitto_rr command line tool for MQTT communication
 * Formats JSON message with token, timestamp, and body content
 * Processes response to extract pure JSON content
 * 
 * @param publish_topic MQTT topic for publishing requests
 * @param sub_topic MQTT topic for subscribing to responses
 * @param body JSON body content for the request message
 * @param result Buffer to store the response message (must be pre-allocated)
 * @return uint8_t Returns TRUE if successful, FALSE on failure
 */
uint8_t get_message(const char *publish_topic, const char *sub_topic, const char *body, char *result)
{
    /* Buffer for constructing MQTT command */
    char mqtt_cmd[512] = {0};
    /* Buffer for storing timestamp string */
    char timestamp[64] = {0};
    uint8_t ret = 0;
    int len = 0;
    int i = 0;

    /* Generate current timestamp for message */
    get_timestamp(timestamp);
    /* Construct mosquitto_rr command with all parameters */
    sprintf(mqtt_cmd, "mosquitto_rr -h 127.0.0.1 -p 1883 -W 5\
            -t %s \
            -m \"{\\\"token\\\":\\\"1\\\", \\\"timestamp\\\":\\\"%s\\\", \\\"body\\\":[%s]}\" \
            -e %s \
            -v \
            -k 5",
            publish_topic, timestamp, body, sub_topic);
    /* Log the constructed command for debugging */
    log_printf(MSG_DEBUG, "cmd: %s\n", mqtt_cmd);

    /* Execute the MQTT command and capture output */
    ret = shell_exec(mqtt_cmd, result);

    /* Validate command execution and non-empty result */
    if (!ret || strlen(result) == 0)
    {
        return FALSE;
    }

    // Remove topic prefix from response to extract pure JSON
    /* Find the start of JSON content (first '{' character) */
    while (result[i] != '{')
    {
        i++;
    }
    /* Calculate length of remaining content */
    len = strlen(result);
    /* Copy JSON content to beginning of result buffer */
    memcpy(result, result + i, len - i);
    /* Null-terminate the result string */
    result[len - i] = '\0';

    return TRUE;
}

/**
 * @brief Retrieve cellular module information via MQTT
 * 
 * This function queries cellular module details including SIM slot, module name,
 * firmware version, and IMEI number through MQTT request-response
 * Parses JSON response and populates CellularModuleInfo structure
 * Handles multiple module entries in the response array
 * 
 * @param cellularModuleInfo Pointer to CellularModuleInfo structure array for storing results
 * @return uint8_t Returns TRUE if successful, FALSE on failure
 */
uint8_t get_cellularModuleInfo(CellularModuleInfo *cellularModuleInfo)
{
    /* Initialize return value to indicate failure by default */
    uint8_t ret = FALSE;
    /* Buffer for storing raw module information response */
    char moduleInfo[1024] = {0};
    int iVal = 0;
    time_t tTimeStamp = 0;

    /* Send MQTT request and receive module information */
    ret = get_message(TOPIC_GET_MODULEINFO_PUB, TOPIC_GET_MODULEINFO_SUB, "", moduleInfo);
    /* Log the received module information for debugging */
    log_printf(MSG_DEBUG, moduleInfo);
    /* Check if MQTT communication was successful */
    if (!ret)
    {
        return ret;
    }
    /* JSON parsing variables */
    cJSON *pjPayLoad = NULL, *pjBody = NULL, *pjItem = NULL;
    /* Parse the JSON response string into cJSON object */
    if ((pjPayLoad = cJSON_Parse(moduleInfo)) == NULL)
        return ret;

    /* Extract and validate token from response */
    if (!GetItemInt(&iVal, pjPayLoad, "token"))
    {
        goto PARSE_ERROR;
    }

    /* Extract and validate timestamp from response */
    if (!GetItemTimeStamp(&tTimeStamp, pjPayLoad, "timestamp"))
    {
        goto PARSE_ERROR;
    }

    /* Get the body array containing module information */
    pjBody = NULL;
    if ((pjBody = cJSON_GetObjectItem(pjPayLoad, "body")) != NULL)
    {
        /* Get number of modules in the response array */
        int iSize = cJSON_GetArraySize(pjBody);
        /* Iterate through each module in the array */
        for (int i = 0; i < iSize; i++)
        {
            pjItem = NULL;
            /* Get the current module item from array */
            pjItem = cJSON_GetArrayItem(pjBody, i);
            /* Extract SIM slot information */
            if (!GetItemStr(cellularModuleInfo[i].simSlot, pjItem, "simSlot"))
            {
                break;
            }

            /* Extract module name information */
            if (!GetItemStr(cellularModuleInfo[i].moduleName, pjItem, "moduleName"))
            {
                break;
            }

            /* Extract firmware version information */
            if (!GetItemStr(cellularModuleInfo[i].firmwareVersion, pjItem, "firmwareVersion"))
            {
                break;
            }

            /* Extract IMEI number information */
            if (!GetItemStr(cellularModuleInfo[i].imei, pjItem, "imei"))
            {
                break;
            }
        }
        /* Set return value to indicate success */
        ret = TRUE;
    }
/* Error handling label for JSON parsing failures */
PARSE_ERROR:
    /* Clean up cJSON objects to prevent memory leaks */
    if (pjPayLoad)
        cJSON_Delete(pjPayLoad);
    return ret;
}

/**
 * @brief Retrieve cellular network information via MQTT
 * 
 * This function queries comprehensive cellular network status including
 * SIM status, network operator, signal quality metrics, and connection details
 * Supports both basic network info and advanced signal metrics (RSRP, RSRQ, SINR)
 * Parses JSON response and populates CellularNetworkInfo structure
 * 
 * @param cellularNetworkInfo Pointer to CellularNetworkInfo structure array for storing results
 * @return uint8_t Returns TRUE if successful, FALSE on failure
 */
uint8_t get_cellularNetworkInfo(CellularNetworkInfo *cellularNetworkInfo)
{
    /* Initialize return value to indicate failure by default */
    uint8_t ret = FALSE;
    /* Buffer for storing raw network information response */
    char networkInfo[1024] = {0};
    int iVal = 0;
    time_t tTimeStamp = 0;

    /* Send MQTT request and receive network information */
    ret = get_message(TOPIC_GET_NETWORKINFO_PUB, TOPIC_GET_NETWORKINFO_SUB, "", networkInfo);
    /* Log the received network information for debugging */
    log_printf(MSG_DEBUG, networkInfo);
    /* Check if MQTT communication was successful */
    if (!ret)
    {
        return ret;
    }
    /* JSON parsing variables */
    cJSON *pjPayLoad = NULL, *pjBody = NULL, *pjItem = NULL;
    /* Parse the JSON response string into cJSON object */
    if ((pjPayLoad = cJSON_Parse(networkInfo)) == NULL)
        return ret;

    /* Extract and validate token from response */
    if (!GetItemInt(&iVal, pjPayLoad, "token"))
    {
        goto PARSE_ERROR;
    }

    /* Extract and validate timestamp from response */
    if (!GetItemTimeStamp(&tTimeStamp, pjPayLoad, "timestamp"))
    {
        goto PARSE_ERROR;
    }

    /* Get the body array containing network information */
    pjBody = NULL;
    if ((pjBody = cJSON_GetObjectItem(pjPayLoad, "body")) != NULL)
    {

        /* Get number of network entries in the response array */
        int iSize = cJSON_GetArraySize(pjBody);
        /* Iterate through each network entry in the array */
        for (int i = 0; i < iSize; i++)
        {
            pjItem = NULL;
            /* Get the current network item from array */
            pjItem = cJSON_GetArrayItem(pjBody, i);
            /* Extract SIM slot identifier */
            if (!GetItemStr(cellularNetworkInfo[i].simSlot, pjItem, "simSlot"))
            {
                break;
            }

            /* Extract SIM card status information */
            if (!GetItemStr(cellularNetworkInfo[i].simStatus, pjItem, "simStatus"))
            {
                break;
            }

            /* Extract Mobile Network Operator (MNO) information */
            if (!GetItemStr(cellularNetworkInfo[i].mno, pjItem, "mno"))
            {
                break;
            }

            /* Extract International Mobile Subscriber Identity (IMSI) */
            if (!GetItemStr(cellularNetworkInfo[i].imsi, pjItem, "imsi"))
            {
                break;
            }

            /* Extract Integrated Circuit Card Identifier (ICCID) */
            if (!GetItemStr(cellularNetworkInfo[i].iccid, pjItem, "iccid"))
            {
                break;
            }

            /* Extract network connection mode (2G/3G/4G/5G) */
            if (!GetItemStr(cellularNetworkInfo[i].mode, pjItem, "mode"))
            {
                break;
            }

            // document version V1.7 - Advanced signal quality metrics
            /* Extract Received Signal Strength Indicator (RSSI) */
            if (!GetItemStr(cellularNetworkInfo[i].rssi, pjItem, "rssi"))
            {
                break;
            }

            /* Extract Reference Signal Received Power (RSRP) */
            if (!GetItemStr(cellularNetworkInfo[i].rsrp, pjItem, "RSRP"))
            {
                break;
            }

            /* Extract Reference Signal Received Quality (RSRQ) */
            if (!GetItemStr(cellularNetworkInfo[i].rsrq, pjItem, "RSRQ"))
            {
                break;
            }

            /* Extract Signal to Interference plus Noise Ratio (SINR) */
            if (!GetItemStr(cellularNetworkInfo[i].sinr, pjItem, "SINR"))
            {
                break;
            }
            // end document version V1.7

            /* Extract IP address assigned to the cellular interface */
            if (!GetItemStr(cellularNetworkInfo[i].ipAddress, pjItem, "ipAddress"))
            {
                break;
            }

            /* Extract network subnet mask */
            if (!GetItemStr(cellularNetworkInfo[i].netMask, pjItem, "netmask"))
            {
                break;
            }

            /* Extract network gateway address */
            if (!GetItemStr(cellularNetworkInfo[i].gateway, pjItem, "gateway"))
            {
                break;
            }

            /* Extract dial-up connection type */
            if (!GetItemStr(cellularNetworkInfo[i].dialType, pjItem, "dialType"))
            {
                break;
            }

            /* Extract dial-up connection status */
            if (!GetItemStr(cellularNetworkInfo[i].dialStatus, pjItem, "dialStatus"))
            {
                break;
            }
        }
        /* Set return value to indicate success */
        ret = TRUE;
    }
/* Error handling label for JSON parsing failures */
PARSE_ERROR:
    /* Clean up cJSON objects to prevent memory leaks */
    if (pjPayLoad)
        cJSON_Delete(pjPayLoad);
    return ret;
}

/**
 * @brief Configure Access Point Name (APN) settings via MQTT
 * 
 * This function sends APN configuration parameters to the cellular module
 * including APN name, username, and password for specific SIM slot
 * Validates the configuration operation status from the response
 * 
 * @param confiInfo Pointer to configAPNInfo structure containing APN settings
 * @param status Buffer to store operation status result (must be pre-allocated)
 * @return uint8_t Returns TRUE if successful, FALSE on failure
 */
uint8_t get_configAPN(configAPNInfo *confiInfo, char *status)
{
    /* Initialize return value to indicate failure by default */
    uint8_t ret = FALSE;
    /* Buffer for constructing request body */
    char body[512] = {0};
    /* Buffer for storing response */
    char result[256] = {0};
    /* Construct JSON body with APN configuration parameters */
    sprintf(body,
            "{\\\"simSlot\\\":\\\"%s\\\",\\\"apnName\\\":\\\"%s\\\",\\\"apnUser\\\":\\\"%s\\\", "
            "\\\"apnPsw\\\":\\\"%s\\\"}",
            confiInfo->simSlot, confiInfo->apnName, confiInfo->apnUser, confiInfo->apnPsw);
    /* Send MQTT request to configure APN and receive response */
    ret = get_message(TOPIC_CONFIG_APN_PUB, TOPIC_CONFIG_APN_SUB, body, result);
    /* Log the response for debugging */
    log_printf(MSG_DEBUG, result);
    /* Check if MQTT communication was successful */
    if (!ret)
    {
        return ret;
    }
    int iVal = 0;
    time_t tTimeStamp;

    /* Parse the JSON response string into cJSON object */
    cJSON *pjPayLoad = NULL;
    if ((pjPayLoad = cJSON_Parse(result)) == NULL)
        return ret;

    /* Extract and validate token from response */
    if (!GetItemInt(&iVal, pjPayLoad, "token"))
    {
        goto PARSE_ERROR;
    }

    /* Extract and validate timestamp from response */
    if (!GetItemTimeStamp(&tTimeStamp, pjPayLoad, "timestamp"))
    {
        goto PARSE_ERROR;
    }

    /* Extract operation status from response */
    if (!GetItemStr(status, pjPayLoad, "status"))
    {
        goto PARSE_ERROR;
    }
    /* Set return value to indicate success */
    ret = TRUE;
/* Error handling label for JSON parsing failures */
PARSE_ERROR:
    /* Clean up cJSON objects to prevent memory leaks */
    if (pjPayLoad)
        cJSON_Delete(pjPayLoad);

    return ret;
}